package BNS.plumbing;

/** This class handles the operation of a virtual clock.
	It is hitched to wall clock time, but can be made to run at arbitrary speed
	(which is useful for playing movies of past games at high speed, or explanatory
	demos at slow speeds).
	Time can be suspended and resumed by calling the suspend() and resume() methods,
	and its speed can be changed at any time by calling setSpeed().
	Threads that wish to suspend themselves until some future game time (as defined
	by this class) must do so by calling waitUntil().
	*/
public class Timing {
	
	protected long startTime;	// milliseconds
	protected int timeSpent;	// milliseconds
	protected boolean suspended;
	private float SPEED;	// the rate at which effective time passes relative to real time.
	
	/** Creates a Timing manager which runs at a multiple of real time specified by its argument. 
		@see Timing#setSpeed(float)
		@param speed the rate at which simulated time will proceed.
		*/
	public Timing(float speed) {
		timeSpent= 0;
		suspended= true;		// implies clock is initially suspended
		setSpeed(speed);
		System.err.println("Timing object created");
		}
	
	/** Creates a Timing manager hitched directly to wall clock. */
	public Timing() { this(1.0f); }
	
	/** alters the speed at which time will subsequently fly. Normally 1.0,
		but if you want a simulation to be replayed at double speed (say), then
		call this method with an argument of 2.0 */
	public void setSpeed(float speed) { 
		System.err.println("setting SPEED to "+speed);
		if (suspended) SPEED=speed;
		else { suspend(); SPEED=speed; proceed(); }
		}
	
	/** Controls progress of the game.
		After this call, actions by the players will be processed 
		until suspend() is called, or until finished() is called.  */
	public synchronized void proceed() {
		if (suspended) {
			startTime= System.currentTimeMillis();
			suspended= false;
			synchronized(this) { notifyAll(); }
			}
		}
	
	/** Controls progress of the game.
		After this call, actions by the players will be ignored, but if
		proceed() is called subsequently, actions will be re-enabled.  */
	public synchronized void suspend() { 
		if (!suspended) {
			timeSpent= getElapsedTime();
			suspended= true;
			synchronized(this) { notifyAll(); }
			}
		}
	
	/** Indicates whether the clock is currently suspended. */
	public boolean suspended() { return suspended; }
	
	/** returns the number of milliseconds elapsed since the game was started (not counting
		those during which the game was suspended). 
		*/
	public int getElapsedTime() {
		if (suspended) return timeSpent;
		else return (int)(timeSpent + (System.currentTimeMillis()-startTime)*SPEED);
		}
	
	/** Wait until a given point in game time occurs.
		This waits indefinitely while the game is suspended; otherwise it runs at
		the game speed until a given amount of game time has passed.
		@param time the time (measured in game milliseconds from the beginning)
			at which to release this thread.
		*/
	public synchronized void waitUntil(String thrID, long time) {
		long remaining= (time-getElapsedTime());
		//System.err.println("waitUntil "+thrID+" thread "+Thread.currentThread()+" at "+
		//					getElapsedTime()+" for "+ remaining +" until "+time);
		try {
			while ((remaining= (int)((time-getElapsedTime())/SPEED)) >0) {
				while(suspended) wait(); 
					//System.err.println("waiting "+thrID+" thread "+startTime+" at "+
					//					getElapsedTime()+" for "+ remaining +" until "+time);
				wait(remaining);
				}
			}
		catch (InterruptedException ie) { System.err.println("clockThread interrupted; WHY?"); }
		}
	
	}
